/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.p071;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import java.util.Comparator;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import com.lucky_byte.pay.jar.Encryptor;
import com.lucky_byte.pay.jar.JdbcRecord;
import com.lucky_byte.pay.jar.JdbcTable;
import com.lucky_byte.pay.jar.Runtime;

/**
 * 银联 ACP 功能模块
 */
public class Lib071
{
	private static final Logger logger = LogManager.getLogger();

	/**
	 * 计算报文签名
	 *
	 * @param packet 报文
	 * @param key_name 签名密钥名称
	 *
	 * @return 签名字符串
	 */
	public static boolean sign(Packet071 packet, String key_name) {
		Encryptor encryptor = Encryptor.getInstance();
		PrivateKeyEntry pkey_entry = encryptor.getPrivateKeyEntry(key_name);
		if (pkey_entry == null) {
			logger.error("加密机中未找到签名证书[{}]，签名失败.", key_name);
			return false;
		}
		X509Certificate cert = (X509Certificate) pkey_entry.getCertificate();
		if (cert == null) {
			logger.error("取签名私钥[{}]关联的公钥证书失败.", key_name);
			return false;
		}
		// 添加 certID (签名私钥证书序号) 及 signMethod (SHA1withRSA)
		packet.set("certId", cert.getSerialNumber().toString());
		packet.set("signMethod", "01");

		String sign_data = getSignData(packet);
		byte[] signature = encryptor.signSHA1WithRSA(key_name,
				sign_data.toLowerCase().getBytes());
		if (signature == null) {
			return false;
		}
		packet.set("signature", new String(Base64.encode(signature)));
		return true;
	}

	/**
	 * 验证报文的签名，当前软件加密机支持 SHA1withRSA 算法
	 * 系统提供 DSA 算法的支持，但是没有整合，因为规范中未提及其它算法.
	 *
	 * 参数 verify_sign_k 没有使用，因为系统通过签名证书序号(certId字段)
	 * 来动态获取证书，这样可以支持多个签名证书.
	 *
	 * @return 成功或失败
	 */
	public static boolean verifySign(Packet071 packet, String key_name) {
		String signature = packet.get("signature");
		if (signature == null) {
			logger.warn("报文缺少[signature]字段，无法验证签名.");
			return false;
		}
		String cert_id = packet.get("certId");
		if (cert_id == null) {
			logger.warn("报文缺少[certId]字段，无法验证签名.");
			return false;
		}
		String sign_method = packet.get("signMethod");
		if (sign_method == null) {
			logger.warn("报文缺少[signMethod]字段，无法验证签名.");
			return false;
		}
		Encryptor encryptor = Encryptor.getInstance();
		if (encryptor == null) {
			return false;
		}
		String sign_data = getSignData(packet);
		if (sign_method.equals("01")) {
			// 直接通过查找加密机中序号匹配的证书进行验证签名
			return encryptor.verifySignSHA1WithRSA(cert_id,
					sign_data.toLowerCase().getBytes(),
					Base64.decode(signature));
		} else {
			logger.error("系统不支持签名方法[{}]，验证签名失败.", sign_method);
			return false;
		}
	}

	/**
	 * 按照字母顺序输出 key=value&key=value... 这样的字符串，其中
	 * key 是报文中的字段名称，value 是字段的值.
	 * <p>
	 * 这个函数在处理时会自动忽略 'signature' 字段.
	 * </p>
	 */
	private static String getSignData(Packet071 packet) {
		List<String> keyset = packet.keySet();
		keyset.sort(new Comparator<String>() {
			@Override
			public int compare(String s1, String s2) {
				return s1.compareTo(s2);
			}
		});
		StringBuilder builder = new StringBuilder();
		for (String key : keyset) {
			if (key.equalsIgnoreCase("signature")) {
				continue;
			}
			builder.append(key);
			builder.append("=");
			builder.append(packet.get(key));
			builder.append("&");
		}
		String encoding = packet.get("encoding");
		if (encoding == null) {
			encoding = "UTF-8";
		}
		String sign_data = builder.substring(0, builder.length() - 1);
//		logger.trace("待签名数据[{}].", sign_data);
		try {
			Encryptor encryptor = Encryptor.getInstance();
			byte[] digest = encryptor.sha1(sign_data.getBytes(encoding));
			return Hex.toHexString(digest);
		} catch (UnsupportedEncodingException e) {
			logger.error("不支持字符编码[{}].", encoding);
			return null;
		}
	}

	/**
	 * 编码身份认证信息
	 */
	private static boolean encodeCustomerInfo(Packet071 packet) {
		String cust_info = packet.get("customerInfo");
		if (cust_info == null) {
			return true;
		}
		byte[] encode_cust_info = Base64.encode(cust_info.getBytes());
		packet.set("customerInfo", new String(encode_cust_info));
		return true;
	}

	/**
	 * 对报文进行编码
	 */
	public static boolean encode(Packet071 packet) {
		if (packet.has("customerInfo")) {
			if (!Lib071.encodeCustomerInfo(packet)) {
				logger.error("编码字段[customerInfo]失败.");
				return false;
			}
		}
		return true;
	}

	/**
	 * 报文解码
	 */
	public static boolean decode(Packet071 packet) {
		String merno = packet.get("merId");
		if (merno == null) {
			logger.error("报文中缺少[merId]字段，编码失败.");
			return false;
		}
		return true;
	}

	/**
	 * 加密敏感字段
	 */
	public static boolean encrypt(Packet071 packet) {
		String merno = packet.get("merId");
		if (merno == null) {
			logger.error("报文中缺少[merId]字段，加密失败.");
			return false;
		}
		if (packet.has("accNo")) {
			String encr_key_name = "acp_encr_" + merno;
			if (!Lib071.encryptAccNo(packet, encr_key_name)) {
				logger.error("加密字段[accNo]失败.");
				return false;
			}
		}
		return true;
	}

	/**
	 * 解密敏感字段
	 */
	public static boolean decrypt(Packet071 packet) {
		String merno = packet.get("merId");
		if (merno == null) {
			logger.error("报文中缺少[merId]字段，解密失败.");
			return false;
		}
		if (packet.has("accNo")) {
			String encr_key_name = "acp_encr_" + merno;
			if (!Lib071.decryptAccNo(packet, encr_key_name)) {
				logger.error("解密字段[accNo]失败.");
				return false;
			}
		}
		return true;
	}

	/**
	 * 采用指定的证书解密敏感字段
	 */
	public static boolean decrypt(Packet071 packet, String key_alias) {
		if (packet.has("accNo")) {
			if (!Lib071.decryptAccNo(packet, key_alias)) {
				logger.error("解密字段[accNo]失败.");
				return false;
			}
		}
		return true;
	}

	/**
	 * 加密账号
	 */
	private static boolean encryptAccNo(Packet071 packet, String key_name) {
		String acc_no = packet.get("accNo");
		if (acc_no == null) {
			return true;
		}
		Encryptor encryptor = Encryptor.getInstance();
		String encode_acc_no =
				encryptor.AcpEncrypt(key_name, acc_no.getBytes());
		if (encode_acc_no == null) {
			logger.error("加密账号字段[accNo]错误.");
			return false;
		}
		packet.set("accNo", encode_acc_no);
		X509Certificate cert = encryptor.getX509Certificate(key_name);
		packet.set("encryptCertId", cert.getSerialNumber().toString());

		return true;
	}

	/**
	 * 解密账号
	 */
	private static boolean decryptAccNo(Packet071 packet, String key_name) {
		String acc_no = packet.get("accNo");
		if (acc_no == null) {
			return true;
		}
		byte[] enc_acc_no;
		try {
			enc_acc_no = Base64.decode(acc_no);
//			ByteUtils.log("BASE64 解码账号：", enc_acc_no);
		} catch (Exception e) {
			logger.warn("[accNo]不是有效的 Base64 编码字符串，不能解密.");
			return false;
		}
		Encryptor encryptor = Encryptor.getInstance();
		byte[] dec_acc_no = encryptor.AcpDecrypt(key_name, enc_acc_no);
		if (dec_acc_no == null) {
			return false;
		}
		try {
			String acc_no_str = new String(dec_acc_no, "utf-8");
			packet.set("accNo", acc_no_str);
		} catch (UnsupportedEncodingException e) {
			logger.error("解密字段[accNo]错误[解密后数据无效].");
			return false;
		}
		return true;
	}

	/**
	 * 将商户的签名证书添加到加密机
	 * 仅用于 LuckPay APP 项目
	 */
	public static boolean loadMerCerts(JdbcRecord mer_record) {
		Encryptor encryptor = Encryptor.getInstance();

		String mer_uuid = mer_record.getString("uuid");
		String alias = mer_uuid + "-sign";

		// 如果加密机中不存在此证书，则加载到加密机中，加载到加密机中后可以提高后续处理速度
		if (!encryptor.contain(alias)) {
			File sign_cert = Runtime.fileFor(
					"/mer/" + mer_uuid + "/certs/sign.pfx");
			if (!sign_cert.exists()) {
				logger.error("商户[{}]的签名证书[{}]不存在.", mer_uuid,
						sign_cert.getAbsolutePath());
				return false;
			}
			String sign_key = mer_record.getString("sign_key");
			if (sign_key == null || sign_key.trim().isEmpty()) {
				logger.error("商户[{}]没有配置签名证书密钥.", mer_uuid);
				return false;
			}
			// 添加到加密机
			if (!encryptor.addPKCS12Cert(sign_cert, sign_key.trim(), alias)) {
				logger.error("添加商户[{}]的签名证书失败.", mer_uuid);
				return false;
			}
			logger.info("添加商户[{}]的签名证书成功.", mer_uuid);
		}
		alias = mer_uuid + "-verify";
		if (!encryptor.contain(alias)) {
			File verify_sign_cert = Runtime.fileFor(
					"/mer/" + mer_uuid + "/certs/verify_sign.cer");
			if (!verify_sign_cert.exists()) {
				logger.error("商户[{}]的验签证书[{}]不存在.", mer_uuid,
						verify_sign_cert.getAbsolutePath());
				return false;
			}
			// 添加到加密机
			if (!encryptor.addX509Cert(verify_sign_cert, alias)) {
				logger.error("添加商户[{}]的验签证书失败.", mer_uuid);
				return false;
			}
			logger.info("添加商户[{}]的验签证书成功.", mer_uuid);
		}
		alias = mer_uuid + "-encrypt";
		if (!encryptor.contain(alias)) {
			File encrypt_cert = Runtime.fileFor(
					"/mer/" + mer_uuid + "/certs/encrypt.cer");
			if (encrypt_cert.exists()) {
				// 添加到加密机
				if (!encryptor.addX509Cert(encrypt_cert, alias)) {
					logger.error("添加商户[{}]的加密证书失败.", mer_uuid);
					return false;
				}
				logger.info("添加商户[{}]的加密证书成功.", mer_uuid);
			}
		}
		return true;
	}

	/**
	 * 通过商户 uuid 加载其证书
	 * 仅用于 LuckPay APP 项目
	 */
	public static boolean loadMerCerts(String mer_uuid) {
		JdbcTable table = JdbcTable.listBy1_NE(
				"web_users", "uuid = ?", mer_uuid);
		if (table == null || table.getRecordCount() == 0) {
			return false;
		}
		return loadMerCerts(table.getRecord(0));
	}

	/**
	 * 清除商户的证书
	 * 仅用于 LuckPay APP 项目
	 *
	 * @param mer_uuid 商户 UUID
	 */
	public static void clearMerCerts(String mer_uuid) {
		Encryptor encryptor = Encryptor.getInstance();

		encryptor.deleteByAlias(mer_uuid + "-sign");
		encryptor.deleteByAlias(mer_uuid + "-verify");
		encryptor.deleteByAlias(mer_uuid + "-encrypt");
	}
}
